﻿using Microsoft.Phone.Net.NetworkInformation;
using System.ComponentModel;
namespace Bango.WindowsPhone7_5.SDK
{
	internal class BangoLogger
	{
		private System.Device.Location.GeoCoordinateWatcher _locationWatcher;

		private static BangoProfile _bangoProfile;
		public static BangoProfile LoggerProfile
		{
			get { return _bangoProfile; }
			set { _bangoProfile = value; }
		}
		
		#region Properties
		
		private System.String _applicationId;
		internal System.String ApplicationId
		{
			get { return _applicationId; }
			set
			{
				_applicationId = value;
				BangoStorage.UpdateSetting<System.String>("ApplicationId", _applicationId);
			}
		}


		private System.Boolean _logEvents = true;
		internal System.Boolean LogEvents
		{
			get { return _logEvents; }
			set { 
				_logEvents = value;
				BangoStorage.UpdateSetting<System.Boolean>("LogEvents", _logEvents); 
			}

		}

		private System.Boolean _useOfflineStorage = true;
		internal System.Boolean UseOfflineStorage
		{
			get { return _useOfflineStorage; }
			set {   
				_useOfflineStorage = value;
				BangoStorage.UpdateSetting<System.Boolean>("UseOfflineStorage", _useOfflineStorage); 
			}
		}


		private System.Int32 _sessionResumePeriod = BangoConstants.SETTING_SESSION_RESUME_PERIOD;
		internal System.Int32 SessionResumePeriod
		{
			get { return _sessionResumePeriod; }
			set { 
				_sessionResumePeriod = value;
				BangoStorage.UpdateSetting<System.Int32>("SessionResumePeriod", _sessionResumePeriod); 
			}
		}

		private System.String _applicationVersion = System.String.Empty;
		internal System.String ApplicationVersion
		{
			get { return _applicationVersion; }
			set { 
				_applicationVersion = value;
				BangoStorage.UpdateSetting<System.String>("ApplicationVersion", _applicationVersion); 
			}
		}


		private System.String _applicationType = System.String.Empty;
		internal System.String ApplicationType
		{
			get { return _applicationType; }
			set { 
				_applicationType = value;
				BangoStorage.UpdateSetting<System.String>("ApplicationType", _applicationType); 
			}
		}

		private System.String _applicationChannel = System.String.Empty;
		internal System.String ApplicationChannel
		{
			get { return _applicationChannel; }
			set { 
				_applicationChannel = value;
				BangoStorage.UpdateSetting<System.String>("ApplicationChannel", _applicationChannel); 
			}
		}

		private System.String _userId = System.String.Empty;
		internal System.String UserId
		{
			get { return _userId; }
			set { 
				_userId = value;
				BangoStorage.UpdateSetting<System.String>("UserId", _userId); 
			}
		}

		private System.String _postURL = BangoConstants.SETTING_POST_URL;
		internal System.String PostURL
		{
			get { return _postURL; }
			set { 
				_postURL = value;
				BangoStorage.UpdateSetting<System.String>("PostURL", _postURL); 
			}
		}

		private System.Boolean _findLocation = true;
		internal System.Boolean FindLocation
		{
			get { return _findLocation; }
			set { 
				_findLocation = value;
				BangoStorage.UpdateSetting<System.Boolean>("FindLocation", _findLocation); 
			}
		}

		private System.String _locationSource = BangoConstants.LOCATION_GPS;
		internal System.String LocationSource
		{
			get { return _locationSource; }
			set { 
				_locationSource = value;
				BangoStorage.UpdateSetting<System.String>("LocationSource", _locationSource); 
			}
		}

		private System.String _userAgent = System.String.Empty;
		internal System.String UserAgent
		{
			get { return _userAgent; }
			set { 
				_userAgent = value; 
				// BangoStorage.UpdateSetting<System.String>("UserAgent", _userAgent); 
			}
		}

		private bool _isRoaming = false;
		internal System.Boolean IsRoaming
		{
			get { return _isRoaming; }
			set
			{
				_isRoaming = value;
				// BangoStorage.UpdateSetting<System.Boolean>("IsRoaming", _isRoaming); 
			}
		}

		private string _connType = BangoConstants.UNKNOWN_CONNECTION;
		internal System.String ConnectionType
		{
			get { return _connType; }
			set
			{
				_connType = value;
				//BangoStorage.UpdateSetting<System.String>("ConnectionType", _connType); 
			}
		}

		private string _opConnType = BangoConstants.UNKNOWN_CONNECTION;
		internal System.String OperatorConnectionType
		{
			get { return _opConnType; }
			set
			{
				_opConnType = value;
				//BangoStorage.UpdateSetting<System.String>("OperatorConnectionType", _opConnType); 
			}
		}
		
		private bool _hasProfiles = false;
		internal System.Boolean HasDBProfiles
		{
			get { return _hasProfiles; }
			set
			{
				_hasProfiles = value;
				BangoStorage.UpdateSetting<System.Boolean>("HasDBProfiles", _hasProfiles);
			}
		}

		#endregion

		#region Initial Setup

		internal BangoLogger()
		{
			LoadSettings();
		}
		
		/// <summary>
		/// Loads any existing settings - required for tombstoning
		/// </summary>
		private void LoadSettings()
		{

			if (BangoStorage.HasSettings())
			{
				HasDBProfiles = BangoStorage.LoadSetting<System.Boolean>("HasDBProfiles", HasDBProfiles);

				LogEvents = BangoStorage.LoadSetting<System.Boolean>("LogEvents", LogEvents);
				FindLocation = BangoStorage.LoadSetting<System.Boolean>("FindLocation", FindLocation);
				UseOfflineStorage = BangoStorage.LoadSetting<System.Boolean>("UseOfflineStorage", UseOfflineStorage);

				SessionResumePeriod = BangoStorage.LoadSetting<System.Int32>("SessionResumePeriod", SessionResumePeriod);

				ApplicationId = BangoStorage.LoadSetting<System.String>("ApplicationId", ApplicationId);
				ApplicationVersion = BangoStorage.LoadSetting<System.String>("ApplicationVersion", ApplicationVersion);
				ApplicationType = BangoStorage.LoadSetting<System.String>("ApplicationType", ApplicationType);
				ApplicationChannel = BangoStorage.LoadSetting<System.String>("ApplicationChannel", ApplicationChannel);
				UserId = BangoStorage.LoadSetting<System.String>("UserId", UserId);
				PostURL = BangoStorage.LoadSetting<System.String>("PostURL", PostURL);
				LocationSource = BangoStorage.LoadSetting<System.String>("LocationSource", LocationSource);
			}
		}

		/// <summary>
		/// Initialises the logging system.
		/// </summary>
		/// <param name="applicationId">The application Id of the application. This should be setup in the Bango Management Tools prior to use.</param>
		internal void InitLogger(System.String applicationId)
		{
			System.Double locationLatitude = 0;
			System.Double locationLongitude = 0;
			System.Double locationAltitude = 0;
			System.Double locationAccuracy = 0;
			System.String operatorName = BangoStatic.GetOperatorName();
			System.String manufacturer = BangoStatic.GetDeviceManufacturer();
			System.String deviceName = BangoStatic.GetDeviceName();
			System.String osVersion = BangoStatic.GetDeviceOSVersion();
			System.String buildId = BangoStatic.GetDeviceBuildID();
			System.String isoCountryCode = BangoStatic.GetCountryCode();

			ApplicationId = applicationId;

			SetApplicationVersion();
			SetUserId();

			LoggerProfile = new BangoProfile(-1, BangoConstants.SDK_TYPE, BangoConstants.SDK_VERSION,
									_applicationId, ApplicationType, ApplicationChannel, ApplicationVersion,
									UserId, BangoConstants.UNKNOWN_CONNECTION,  SessionResumePeriod,false,
									LocationSource, locationAltitude, locationLatitude, locationLongitude, locationAccuracy,
									operatorName, operatorName, isoCountryCode,BangoConstants.HARD_CODED_DEVICE_BUILD_BRAND, manufacturer, 
									deviceName, BangoConstants.HARD_CODED_DEVICE_BUILD_PRODUCT, osVersion, buildId,
									BangoConstants.UNKNOWN_CONNECTION, operatorName);

			//Set connection Manager to update connection details, and listen for any future changes
			GetConnectionDetails();

			//Set location updates to update location. This goes after the logging of the profile as you dont have it at time of logging
			GetLocation();
		}


		/// <summary>
		/// Reloads Logger Profile. Used when resuming app from idle state.
		/// </summary>
		internal void ReloadProfile()
		{
			if (LoggerProfile == null)
			{
				InitLogger(ApplicationId);
			}
		}

		/// <summary>
		/// Saves profile in memory to offline storage, 
		/// or posts events if offline storage not in use.
		/// </summary>
		internal void SaveProfile()
		{
			if (UseOfflineStorage)
			{
				LoggerProfile.SaveProfile(UseOfflineStorage);
				HasDBProfiles = true;
			}
			else
			{
				// If offline storage is not in use, post events
				PostEvents(true);
			}
		}
		
		#endregion

		#region Background Worker methods

		void bwPoster_DoWork(object sender, DoWorkEventArgs e)
		{            
			BangoPoster poster = (BangoPoster) e.Argument;
			poster.PostEvents();
		}

		#endregion

		#region Posting Data

		/// <summary>
		/// Posts any stored events, with an option to post the current profile.
		/// </summary>
		/// <param name="postCurrent">Whether or not to post the current profile.</param>
		internal void PostEvents(bool postCurrent)
		{
			PostStoredEvents();

			if (postCurrent)
			{
				PostCurrentEvents();
			}  
		}

		private void PostStoredEvents()
		{
			BangoProfile profileToPost;
			BangoStorage bangoStore;

			try
			{
				if (HasDBProfiles && LogEvents && BangoStatic.IsConnected() && (!_isRoaming || IsWifiConnection()))
				{
					bangoStore = new BangoStorage(UseOfflineStorage);

					foreach (StoredProfile sProfile in bangoStore.StoredProfiles())
					{
						if (!sProfile.IsPosting)
						{
							profileToPost = sProfile.GetPostProfile();
							if (profileToPost.Events.Count > 0  && (!sProfile.Posted))
							{
								sProfile.IsPosting = true;
								PostProfile(sProfile);
							}
							else
							{
								bangoStore.DeleteProfile(sProfile);
							}
						}
					}
					bangoStore.SaveChangesToDB();
				}
			}
			catch (System.Exception ex)
			{
				System.Console.WriteLine(ex.Message);
			}
			finally
			{
			}
		}

		private void PostCurrentEvents()
		{
			BangoProfile profileToPost;
			try
			{
				if (LogEvents && BangoStatic.IsConnected() && (!_isRoaming || IsWifiConnection()))
				{
					profileToPost = LoggerProfile.GetPostProfile();

					if (profileToPost.Events.Count > 0)
					{
						PostProfile(profileToPost);
					}
					
				}
			}
			catch (System.Exception ex)
			{
				System.Console.WriteLine(ex.Message);
			}
			finally
			{
			}
		}

		private void PostProfile(IBangoPostable bProfile)
		{
			BackgroundWorker bwPoster; 
			bwPoster = new BackgroundWorker();
			bwPoster.DoWork += bwPoster_DoWork;
			bwPoster.RunWorkerAsync(new BangoPoster(this, bProfile));
		}

		#endregion

		#region Logging

		/// <summary>
		/// Logs an event to the internal logging engine.
		/// </summary>
		/// <param name="eventName">The name of the event to log.</param>
		/// <param name="errorLevel">The error level of the event to log.</param>
		/// <param name="eventValue">The value of the event to log.</param>
		/// <param name="eventDetail">The detail of the event to log.</param>
		/// <param name="errorId">The error Id of the event to log.</param>
		/// <param name="eventParameters">Any custom parameters to log with events.</param>
		/// <returns>The internal id of the event.</returns>
		private System.Int32 LogEvent(System.String eventName, System.Int32 errorLevel, System.String eventValue, System.String eventDetail, System.String errorId, System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.String, System.String>> eventParameters)
		{
			System.Int32 returnValue = -1;
			System.DateTime eventTime = System.DateTime.UtcNow;
			System.DateTime eventLocalTime = System.DateTime.Now;
			System.Collections.Generic.List<BangoEventParameter> eventParametersPairs = null;

			if (LogEvents)
			{
				eventParametersPairs = new System.Collections.Generic.List<BangoEventParameter>();
				if (eventParameters != null)
				{
					foreach (System.Collections.Generic.KeyValuePair<System.String, System.String> eventParameter in eventParameters)
					{
						eventParametersPairs.Add(new BangoEventParameter(eventParameter.Key, eventParameter.Value));
					}
				}

				returnValue = LoggerProfile.LogEvent(UseOfflineStorage, ConnectionType, OperatorConnectionType, errorLevel, eventName, eventDetail, eventValue, errorId, eventTime, eventLocalTime, eventParametersPairs);
			}
			return returnValue;
		}

		/// <summary>
		/// Logs an event to the internal logging engine.
		/// </summary>
		/// <param name="eventName">The name of the event to log.</param>
		/// <param name="eventDetail">The detail of the event to log.</param>
		/// <param name="eventValue">The value of the event to log.</param>
		/// <param name="eventParameters">Any custom parameters to log with events.</param>
		/// <returns>The internal id of the event.</returns>
		internal System.Int32 LogEvent(System.String eventName, System.String eventValue, System.String eventDetail, System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.String, System.String>> eventParameters)
		{
			return LogEvent(eventName, BangoConstants.ERROR_NONE, eventValue, eventDetail, "", eventParameters);
		}

		/// <summary>
		/// Logs an the session start event to the internal logging engine.
		/// </summary>
		/// <param name="eventValue">The value of the event to log.</param>
		/// <param name="eventDetail">The detail of the event to log.</param>
		/// <param name="eventParameters">Any custom parameters to log with events.</param>
		/// <returns>The internal id of the event.</returns>
		internal System.Int32 LogEventStart(System.String eventValue, System.String eventDetail, System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.String, System.String>> eventParameters)
		{
			return LogEvent(BangoSessionEvents.SESSION_START, eventValue, eventDetail, eventParameters);
		}

		/// <summary>
		/// Logs an the session end event to the internal logging engine.
		/// </summary>
		/// <param name="eventValue">The value of the event to log.</param>
		/// <param name="eventDetail">The detail of the event to log.</param>
		/// <param name="eventParameters">Any custom parameters to log with events.</param>
		/// <returns>The internal id of the event.</returns>
		internal System.Int32 LogEventEnd(System.String eventValue, System.String eventDetail, System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.String, System.String>> eventParameters)
		{
			return LogEvent(BangoSessionEvents.SESSION_END, eventValue, eventDetail, eventParameters);
		}

		/// <summary>
		/// Logs an error event to the internal logging engine.
		/// </summary>
		/// <param name="errorId">The error Id of the event to log.</param>
		/// <param name="message">The error message.</param>
		/// <param name="errorClass">The class in which the error occurred.</param>
		/// <param name="eventParameters">Any custom parameters to log with events.</param>
		/// <returns>The internal id of the error.</returns>
		internal System.Int32 LogEventError(System.String errorId, System.String message, System.String errorClass, System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.String, System.String>> eventParameters)
		{
			return LogEvent(BangoSessionEvents.EVENT_ERROR, BangoConstants.ERROR_USER, errorClass, message, errorId, eventParameters);
		}

		/// <summary>
		/// Logs a critical internal error event to the internal logging engine.
		/// </summary>
		/// <param name="ex">The error that was raised.</param>
		/// <returns>The internal id of the error.</returns>
		internal System.Int32 LogInternalErrorCritical(System.Exception ex)
		{
			return LogInternalError(ex, BangoConstants.ERROR_INTERNAL_CRITICAL);
		}

		/// <summary>
		/// Logs a internal warning error event to the internal logging engine.
		/// </summary>
		/// <param name="ex">The error that was raised.</param>
		/// <returns>The internal id of the error.</returns>
		internal System.Int32 LogInternalErrorWarning(System.Exception ex)
		{
			return LogInternalError(ex, BangoConstants.ERROR_INTERNAL_WARNING);
		}

		/// Logs an internal error event to the internal logging engine.
		/// </summary>
		/// <param name="ex">The error that was raised.</param>
		/// <param name="errorLevel">The error level of the error (Warning/Critical).</param>
		/// <returns>The internal id of the error.</returns>
		private System.Int32 LogInternalError(System.Exception ex, System.Int32 errorLevel)
		{
			return LogEvent("", errorLevel, ex.StackTrace, ex.Message, "", null);
		}

		#endregion

		#region Location Info

		/// <summary>
		/// Initialises the location finder if required.
		/// </summary>
		private void GetLocation()
		{
			if (FindLocation)
			{
				if (LocationSource == BangoConstants.LOCATION_CELL)
				{
					_locationWatcher = new System.Device.Location.GeoCoordinateWatcher(System.Device.Location.GeoPositionAccuracy.High);
				}
				else
				{
					_locationWatcher = new System.Device.Location.GeoCoordinateWatcher(System.Device.Location.GeoPositionAccuracy.Default);
				}
				_locationWatcher.MovementThreshold = 20;

				// Add event handlers for StatusChanged and PositionChanged events
				_locationWatcher.StatusChanged += new System.EventHandler<System.Device.Location.GeoPositionStatusChangedEventArgs>(_locationWatcher_StatusChanged);
				_locationWatcher.PositionChanged += new System.EventHandler<System.Device.Location.GeoPositionChangedEventArgs<System.Device.Location.GeoCoordinate>>(_locationWatcher_PositionChanged);

				// Start data acquisition
				_locationWatcher.Start();
			}
		}

		/// <summary>
		/// Destroys the location finder.
		/// </summary>
		internal void EndLocationUpdates()
		{
			if (FindLocation && _locationWatcher != null)
			{
				_locationWatcher.Stop();
				//_locationWatcher.StatusChanged -= new System.EventHandler<System.Device.Location.GeoPositionStatusChangedEventArgs>(_locationWatcher_StatusChanged);
				//_locationWatcher.PositionChanged -= new System.EventHandler<System.Device.Location.GeoPositionChangedEventArgs<System.Device.Location.GeoCoordinate>>(_locationWatcher_PositionChanged);
				_locationWatcher = null;
			}
		}

		/// <summary>
		/// Handler for the location StatusChanged event.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void _locationWatcher_StatusChanged(object sender, System.Device.Location.GeoPositionStatusChangedEventArgs e)
		{
			switch (e.Status)
			{
				case System.Device.Location.GeoPositionStatus.Disabled:
					// The location service is disabled or unsupported.
					// Alert the user
					EndLocationUpdates();
					break;
			}

		}

		/// <summary>
		/// Handler for the location PositionChanged event.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void _locationWatcher_PositionChanged(object sender, System.Device.Location.GeoPositionChangedEventArgs<System.Device.Location.GeoCoordinate> e)
		{

			if (!e.Position.Location.IsUnknown)
			{
				if (double.IsNaN(e.Position.Location.Altitude))
				{
					e.Position.Location.Altitude = 0;
				}

				LoggerProfile.UpdateProfileLocation(e.Position.Location.Latitude, e.Position.Location.Longitude, e.Position.Location.Altitude, e.Position.Location.HorizontalAccuracy);
				EndLocationUpdates();
			}
		}

		#endregion

		#region Device Info, Connection Status
		/// <summary>
		/// Sets the application version if it's not been specified.
		/// </summary>
		private void SetApplicationVersion()
		{
			System.String[] version;
			try
			{
				if (System.String.IsNullOrEmpty(ApplicationVersion))
				{
					version = System.Reflection.Assembly.GetCallingAssembly().FullName.Split(',');
					if (version.Length > 1 && version[1].IndexOf('=') > -1)
					{

						ApplicationVersion = version[1].Substring(version[1].IndexOf('=') + 1);
					}
				}
				if (System.String.IsNullOrEmpty(ApplicationVersion))
				{
					ApplicationVersion = BangoConstants.UNKNOWN_VERSION;
				}

			}
			catch (System.Exception ex)
			{
				ApplicationVersion = BangoConstants.ERROR_VERSION;
				LogInternalErrorWarning(ex);
			}

		}

		/// <summary>
		/// Sets the user id if it's not been specified.
		/// </summary>
		private void SetUserId()
		{
			try
			{
				if (System.String.IsNullOrEmpty(UserId))
				{
					UserId = BangoStatic.GetUserID();
				}
				if (System.String.IsNullOrEmpty(UserId))
				{
					UserId = BangoConstants.UNKNOWN_USERID;
				}
			}
			catch (System.Exception ex)
			{
				UserId = BangoConstants.UNKNOWN_USERID;
				LogInternalErrorWarning(ex);
			}
		}

		#region Connection Status

		private void GetConnectionDetails()
		{
			SetConnectionProperties(this, null);
			DeviceNetworkInformation.NetworkAvailabilityChanged += new System.EventHandler<NetworkNotificationEventArgs>(SetConnectionProperties);
		}

		private void SetConnectionProperties(object sender, NetworkNotificationEventArgs nnEventArgs)
		{
			NetworkInterfaceInfo nInfo = null;

			IsRoaming = false;
			ConnectionType = BangoConstants.UNKNOWN_CONNECTION;
			OperatorConnectionType = BangoConstants.UNKNOWN_CONNECTION;

			if (nnEventArgs == null)
			{
				nInfo = BangoStatic.GetActiveNetworkInterfaceInfo();
			}
			else
			{
				nInfo = nnEventArgs.NetworkInterface;
			}

			if (nInfo != null)
			{
				switch (nInfo.Characteristics)
				{
					case NetworkCharacteristics.Roaming:
						IsRoaming = true;
						break;
					default:
						break;
				}

				switch (nInfo.InterfaceType)
				{
					case NetworkInterfaceType.Wireless80211:
					case NetworkInterfaceType.Ethernet:
						ConnectionType = BangoConstants.WIFI_CONNECTION;
						break;
					case NetworkInterfaceType.MobileBroadbandCdma:
					case NetworkInterfaceType.MobileBroadbandGsm:
						ConnectionType = BangoConstants.OPERATOR_CONNECTION;
						switch (nInfo.InterfaceSubtype)
						{
							case NetworkInterfaceSubType.Cellular_GPRS:
								OperatorConnectionType = "GPRS";
								break;
							case NetworkInterfaceSubType.Cellular_1XRTT: //TODO - CONFIRM THESE
								OperatorConnectionType = "CDMA";
								break;
							case NetworkInterfaceSubType.Cellular_EVDO:
								OperatorConnectionType = "EVDO_0";
								break;
							case NetworkInterfaceSubType.Cellular_EDGE:
								OperatorConnectionType = "EDGE";
								break;
							case NetworkInterfaceSubType.Cellular_3G: //TODO - CONFIRM THESE
								OperatorConnectionType = "CDMA";
								break;
							case NetworkInterfaceSubType.Cellular_HSPA:
								OperatorConnectionType = "HSPA";
								break;
							case NetworkInterfaceSubType.Cellular_EVDV: //TODO - CONFIRM THESE
								OperatorConnectionType = "CDMA";
								break;
							default:
								break;
						}
						break;
					case NetworkInterfaceType.None:
					default:
						break;
				}
			}
			LoggerProfile.UpdateProfileConnectionDetails(IsRoaming, ConnectionType, OperatorConnectionType);
		}

		private bool IsWifiConnection()
		{
			return (OperatorConnectionType == BangoConstants.WIFI_CONNECTION);
		}

		#endregion

		#endregion
		
		public string viewBatch()
		{
			BangoProfile bProfile = _bangoProfile.GetPostProfile();

			System.Runtime.Serialization.Json.DataContractJsonSerializer jsonSerializer;
			System.String jsonData = System.String.Empty;

			jsonSerializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(BangoProfile));
			using (System.IO.MemoryStream jsonMemory = new System.IO.MemoryStream())
			{
				bProfile.ConnectionType = ConnectionType;
				bProfile.OperatorConnectionType = OperatorConnectionType;
				jsonSerializer.WriteObject(jsonMemory, bProfile);

				using (System.IO.StreamReader jsonStream = new System.IO.StreamReader(jsonMemory))
				{
					jsonMemory.Position = 0;
					jsonData = jsonStream.ReadToEnd();
				}
			}

			return jsonData;
		}
	}
}
